/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Hardware.h"
#include "Channel.h"
#include <math/utl.h>
#include <logs/Log.h>

#pragma comment(lib,"openal32.lib")

namespace liba {
namespace noise {
namespace hard {
namespace openal {

class VolumeConstraint
	: public properties::Constraint<double>
{
public:
	virtual double Process(double value) LIBA_OVERRIDE
	{
		return math::clamp(value, 0.0, 1.0);
	}
};

Hardware::Hardware():
	exactVolume( 1 ),
	listenerFront(math::Vector3<float>(0, 0, 1)),
	listenerUp(math::Vector3<float>(0, 1, 0)),
#ifndef IPHONE_VERSION
	max_playing_channels( 12 )
#else
	max_playing_channels( 4 )
#endif
{
	this->listenerPosition.Changed().connect(boost::bind(&Hardware::ListenerPositionChanged, this, _1));
	this->RegisterProperty(listener_position_name, &this->listenerPosition);
	this->listenerSpeed.Changed().connect(boost::bind(&Hardware::ListenerSpeedChanged, this, _1));
	this->RegisterProperty(listener_speed_name, &this->listenerSpeed);
	this->listenerFront.Changed().connect(boost::bind(&Hardware::ListenerFrontChanged, this, _1));
	this->RegisterProperty(listener_front_name, &this->listenerFront);
	this->listenerUp.Changed().connect(boost::bind(&Hardware::ListenerUpChanged, this, _1));
	this->RegisterProperty(listener_up_name, &this->listenerUp);
	this->exactVolume.AddConstraint(std::auto_ptr<properties::Constraint<double> >(new VolumeConstraint()));
	this->exactVolume.Changed().connect(boost::bind(&Hardware::ExactVolumeChanged, this, _1));
	this->RegisterProperty(global_volume_name, &this->exactVolume);

	device = alcOpenDevice( 0 );
	if( device == 0 )
		THROW(Gluck( __FUNCTION__, "OpenAL not installed" ));
	context = alcCreateContext( device, 0 );
	if( !context )
		THROW(Gluck( __FUNCTION__, "OpenAL is corrupted" ));

	//Set active context
	alcGetError( device );
	alcMakeContextCurrent( context );
	if( alcGetError(device) != ALC_NO_ERROR )
		THROW(Gluck( __FUNCTION__, "OpenAL: failed to install context" ));

	// Clear Error Code
	alGetError();
	alcGetError( device );

	ALfloat listenerPos[] = {0.0,0.0,0.0};
	ALfloat listenerVel[] = {0.0,0.0,0.0};
	ALfloat	listenerOri[] = {0.0,0.0,-1.0, 0.0,1.0,0.0};	// Listener facing into the screen

	// Set Listener default attributes
	ALint error;

	// Position ...
	alListenerfv( AL_POSITION, listenerPos );
	if( (error = alGetError()) != AL_NO_ERROR )
		THROW(Gluck( __FUNCTION__, "OpenAL: failed to install position" ));

	// Velocity ...
	alListenerfv( AL_VELOCITY, listenerVel );
	if( (error = alGetError()) != AL_NO_ERROR )
		THROW(Gluck( __FUNCTION__, "OpenAL: failed to install velocity" ));

	// Orientation ...
	alListenerfv( AL_ORIENTATION, listenerOri );
	if( (error = alGetError()) != AL_NO_ERROR )
		THROW(Gluck( __FUNCTION__, "OpenAL: failed to install orientation" ));
}

Hardware::~Hardware()
{
	for( ChannelStaticList::iterator it = static_channels.begin(); it != static_channels.end(); ++it )
	{
		(*it)->on_hardware_delete();
	}
	for( ChannelStreamingList::iterator it = streaming_channels.begin(); it != streaming_channels.end(); ++it )
	{
		(*it)->on_hardware_delete();
	}

	//Disable context
	alcMakeContextCurrent( 0 );
	//Release context(s)
	alcDestroyContext( context );
	//Close device
	alcCloseDevice( device );
}

hard::Channel * Hardware::create_static_channel( const Atom & channel_type, const filesystem::String & name)
{
	return new ChannelStatic(this, channel_type, name );
}
hard::Channel * Hardware::create_streaming_channel( const Atom & channel_type )
{
	return new ChannelStreaming(this, channel_type, 5 );
}

void Hardware::life_cycle(double delta_time)
{
	alcMakeContextCurrent( context );
	ChannelStaticList::iterator it = playing_channels.begin();
	while( it != playing_channels.end() )
	{
		(*it)->life_cycle(delta_time, listenerPosition);
		if( (*it)->is_finished() )
			it = playing_channels.erase( it );
		else
			++it;
	}

///	logs::log() << "Hardware::life_cycle - " << playing_channels.size() << " channels\n";

	playing_channels.sort( ChannelStatic::SortPredicate );
///	std::sort(.begin(), playing_channels.end(), ChannelStatic::SortPredicate );

	it = playing_channels.begin();
	for( int counter = 0; it != playing_channels.end(); ++counter, ++it )
	{
		if( counter < max_playing_channels && !(*it)->in_existance() )
		{
			(*it)->come_to_existance();
		}
		if( counter >= max_playing_channels && (*it)->in_existance() )
		{
			(*it)->begone();
		}
	}

	{
		ChannelStreamingList::iterator it = streaming_channels.begin();
		for( ; it != streaming_channels.end(); ++it )
			(*it)->life_cycle(delta_time);
	}

	ALfloat	listenerOri[] = { listenerFront.Get().x, listenerFront.Get().y, -listenerFront.Get().z,
								listenerUp.Get().x, listenerUp.Get().y, listenerUp.Get().z };
	// Orientation ...
	alListenerfv( AL_ORIENTATION, listenerOri );
}

void Hardware::ListenerPositionChanged(const math::Vector3<float>& value)
{
	ALfloat listenerPos[] = { value[0], value[1], value[2] };
	alListenerfv( AL_POSITION, listenerPos );
}

void Hardware::ListenerSpeedChanged(const math::Vector3<float>& value)
{
	ALfloat listenerVel[] = { value[0], value[1], value[2] };
	alListenerfv( AL_VELOCITY, listenerVel );
}

void Hardware::ListenerFrontChanged(const math::Vector3<float>& value)
{
	// Still nothing here
}

void Hardware::ListenerUpChanged(const math::Vector3<float>& value)
{
	// Still nothing here
}

void Hardware::ExactVolumeChanged(double value)
{
	alListenerf( AL_GAIN, 1.0f );
}

#ifdef IPHONE_VERSION
	void Hardware::app_goes_background()
	{
		alcMakeContextCurrent(0);
	}
	
	void Hardware::app_goes_active()
	{
		alcMakeContextCurrent(context);
	}
#endif
	

} // namespace openal
} // namespace hard
} // namespace noise
} // namesapce liba

